| @@ -3,6 +3,7 @@ | ||
| 3 | 3 | from django.conf.urls import url | 
| 4 | 4 |  | 
| 5 | 5 | from account import views as account_views | 
| 6 | +from group import views as group_views | |
| 6 | 7 | from photo import views as photo_views | 
| 7 | 8 |  | 
| 8 | 9 |  | 
| @@ -13,6 +14,11 @@ urlpatterns = [ | ||
| 13 | 14 | url(r'^u/login$', account_views.user_login_api, name='user_login_api'), # 用户登录 | 
| 14 | 15 | ] | 
| 15 | 16 |  | 
| 17 | +urlpatterns = [ | |
| 18 | + url(r'^g/create$', group_views.group_create_api, name='group_create_api'), # 群组创建 | |
| 19 | + url(r'^g/join$', group_views.group_join_api, name='group_join_api'), # 申请加群 | |
| 20 | +] | |
| 21 | + | |
| 16 | 22 | urlpatterns += [ | 
| 17 | 23 | url(r'^uuid_init$', photo_views.uuid_init, name='uuid_init'), # 生成唯一标识 | 
| 18 | 24 | url(r'^uuid$', photo_views.uuid, name='uuid'), # 获取唯一标识 | 
| @@ -10,3 +10,8 @@ | ||
| 10 | 10 | 4010 —— 参数错误 | 
| 11 | 11 | 4011 —— 摄影师不存在 | 
| 12 | 12 | 4012 —— 照片已存在 | 
| 13 | + | |
| 14 | + | |
| 15 | +3、群组信息 —— 402 | |
| 16 | + 4020 —— 群组不存在 | |
| 17 | + 4021 —— 群组已锁定 | 
| @@ -0,0 +1,25 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django.contrib import admin | |
| 4 | + | |
| 5 | +from group.models import GroupInfo, GroupUserInfo, GroupPhotoInfo | |
| 6 | + | |
| 7 | + | |
| 8 | +class GroupInfoAdmin(admin.ModelAdmin): | |
| 9 | +    list_display = ('group_id', 'group_name', 'group_desc', 'group_from', 'group_lock', 'status', 'created_at', 'updated_at') | |
| 10 | +    list_filter = ('group_from', 'group_lock', 'status') | |
| 11 | + | |
| 12 | + | |
| 13 | +class GroupUserInfoAdmin(admin.ModelAdmin): | |
| 14 | +    list_display = ('group_id', 'user_id', 'nickname', 'admin', 'user_status', 'passed_at', 'refused_at', 'status', 'created_at', 'updated_at') | |
| 15 | +    list_filter = ('user_status', 'status') | |
| 16 | + | |
| 17 | + | |
| 18 | +class GroupPhotoInfoAdmin(admin.ModelAdmin): | |
| 19 | +    list_display = ('group_id', 'user_id', 'nickname', 'photo_path', 'photo_thumbnail_path', 'status', 'created_at', 'updated_at') | |
| 20 | +    list_filter = ('status', ) | |
| 21 | + | |
| 22 | + | |
| 23 | +admin.site.register(GroupInfo, GroupInfoAdmin) | |
| 24 | +admin.site.register(GroupUserInfo, GroupUserInfoAdmin) | |
| 25 | +admin.site.register(GroupPhotoInfo, GroupPhotoInfoAdmin) | 
| @@ -0,0 +1,69 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +from __future__ import unicode_literals | |
| 3 | + | |
| 4 | +from django.db import models, migrations | |
| 5 | + | |
| 6 | + | |
| 7 | +class Migration(migrations.Migration): | |
| 8 | + | |
| 9 | + dependencies = [ | |
| 10 | + ] | |
| 11 | + | |
| 12 | + operations = [ | |
| 13 | + migrations.CreateModel( | |
| 14 | + name='GroupInfo', | |
| 15 | + fields=[ | |
| 16 | +                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
| 17 | +                ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', verbose_name='status')), | |
| 18 | +                ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), | |
| 19 | +                ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), | |
| 20 | +                ('group_id', models.CharField(null=True, max_length=255, blank=True, help_text='\u7fa4\u7ec4\u552f\u4e00\u6807\u8bc6', unique=True, verbose_name='group_id', db_index=True)), | |
| 21 | +                ('group_name', models.CharField(help_text='\u7fa4\u7ec4\u540d\u79f0', max_length=255, null=True, verbose_name='group_name', blank=True)), | |
| 22 | +                ('group_desc', models.TextField(help_text='\u7fa4\u7ec4\u63cf\u8ff0', null=True, verbose_name='group_desc', blank=True)), | |
| 23 | +                ('group_from', models.IntegerField(default=0, help_text='\u7fa4\u7ec4\u6765\u6e90', verbose_name='group_from', choices=[(0, 'APP \u5efa\u7fa4'), (1, 'SESSION \u5efa\u7fa4')])), | |
| 24 | +                ('group_lock', models.BooleanField(default=False, help_text='\u7fa4\u7ec4\u9501\u5b9a', verbose_name='group_lock')), | |
| 25 | + ], | |
| 26 | +            options={ | |
| 27 | + 'verbose_name': 'groupinfo', | |
| 28 | + 'verbose_name_plural': 'groupinfo', | |
| 29 | + }, | |
| 30 | + ), | |
| 31 | + migrations.CreateModel( | |
| 32 | + name='GroupPhotoInfo', | |
| 33 | + fields=[ | |
| 34 | +                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
| 35 | +                ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', verbose_name='status')), | |
| 36 | +                ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), | |
| 37 | +                ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), | |
| 38 | +                ('group_id', models.CharField(max_length=255, blank=True, help_text='\u7fa4\u7ec4\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='group_id', db_index=True)), | |
| 39 | +                ('user_id', models.CharField(help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, null=True, verbose_name='user_id', blank=True)), | |
| 40 | +                ('nickname', models.CharField(help_text='\u7528\u6237\u7fa4\u7ec4\u6635\u79f0', max_length=255, null=True, verbose_name='nickname', blank=True)), | |
| 41 | +                ('photo_path', models.CharField(help_text='\u7167\u7247\u5b58\u653e\u8def\u5f84', max_length=255, null=True, verbose_name='photo_path', blank=True)), | |
| 42 | +                ('photo_thumbnail_path', models.CharField(help_text='\u7167\u7247\u7f29\u7565\u56fe\u5b58\u653e\u8def\u5f84', max_length=255, null=True, verbose_name='photo_thumbnail_path', blank=True)), | |
| 43 | + ], | |
| 44 | +            options={ | |
| 45 | + 'verbose_name': 'groupuserinfo', | |
| 46 | + 'verbose_name_plural': 'groupuserinfo', | |
| 47 | + }, | |
| 48 | + ), | |
| 49 | + migrations.CreateModel( | |
| 50 | + name='GroupUserInfo', | |
| 51 | + fields=[ | |
| 52 | +                ('id', models.AutoField(verbose_name='ID', serialize=False, auto_created=True, primary_key=True)), | |
| 53 | +                ('status', models.BooleanField(default=True, help_text='\u72b6\u6001', verbose_name='status')), | |
| 54 | +                ('created_at', models.DateTimeField(help_text='\u521b\u5efa\u65f6\u95f4', verbose_name='created_at', auto_now_add=True)), | |
| 55 | +                ('updated_at', models.DateTimeField(help_text='\u66f4\u65b0\u65f6\u95f4', verbose_name='updated_at', auto_now=True)), | |
| 56 | +                ('group_id', models.CharField(max_length=255, blank=True, help_text='\u7fa4\u7ec4\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='group_id', db_index=True)), | |
| 57 | +                ('user_id', models.CharField(help_text='\u7528\u6237\u552f\u4e00\u6807\u8bc6', max_length=255, null=True, verbose_name='user_id', blank=True)), | |
| 58 | +                ('nickname', models.CharField(help_text='\u7528\u6237\u7fa4\u7ec4\u6635\u79f0', max_length=255, null=True, verbose_name='nickname', blank=True)), | |
| 59 | +                ('admin', models.BooleanField(default=False, help_text='\u7fa4\u7ec4\u7ba1\u7406\u5458', verbose_name='admin')), | |
| 60 | +                ('user_status', models.IntegerField(default=0, verbose_name='user_status', choices=[(0, '\u7533\u8bf7\u4e2d'), (1, '\u5df2\u901a\u8fc7'), (2, '\u5df2\u62d2\u7edd')])), | |
| 61 | +                ('passed_at', models.DateTimeField(help_text='\u901a\u8fc7\u65f6\u95f4', null=True, verbose_name='passed_at', blank=True)), | |
| 62 | +                ('refused_at', models.DateTimeField(help_text='\u62d2\u7edd\u65f6\u95f4', null=True, verbose_name='refused_at', blank=True)), | |
| 63 | + ], | |
| 64 | +            options={ | |
| 65 | + 'verbose_name': 'groupuserinfo', | |
| 66 | + 'verbose_name_plural': 'groupuserinfo', | |
| 67 | + }, | |
| 68 | + ), | |
| 69 | + ] | 
| @@ -0,0 +1,19 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | +from __future__ import unicode_literals | |
| 3 | + | |
| 4 | +from django.db import models, migrations | |
| 5 | + | |
| 6 | + | |
| 7 | +class Migration(migrations.Migration): | |
| 8 | + | |
| 9 | + dependencies = [ | |
| 10 | +        ('group', '0001_initial'), | |
| 11 | + ] | |
| 12 | + | |
| 13 | + operations = [ | |
| 14 | + migrations.AddField( | |
| 15 | + model_name='groupinfo', | |
| 16 | + name='session_id', | |
| 17 | + field=models.CharField(max_length=255, blank=True, help_text='\u7167\u7247\u7ec4\u552f\u4e00\u6807\u8bc6', null=True, verbose_name='session_id', db_index=True), | |
| 18 | + ), | |
| 19 | + ] | 
| @@ -0,0 +1,101 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django.conf import settings | |
| 4 | +from django.db import models | |
| 5 | +from django.utils.translation import ugettext_lazy as _ | |
| 6 | + | |
| 7 | +from pai2.basemodels import CreateUpdateMixin | |
| 8 | + | |
| 9 | + | |
| 10 | +class GroupInfo(CreateUpdateMixin): | |
| 11 | + APP_GROUP = 0 | |
| 12 | + SESSION_GROUP = 1 | |
| 13 | + | |
| 14 | + GROUP_FROM = ( | |
| 15 | + (APP_GROUP, u'APP 建群'), | |
| 16 | + (SESSION_GROUP, u'SESSION 建群'), | |
| 17 | + ) | |
| 18 | + | |
| 19 | + group_id = models.CharField(_(u'group_id'), max_length=255, blank=True, null=True, help_text=u'群组唯一标识', db_index=True, unique=True) | |
| 20 | + group_name = models.CharField(_(u'group_name'), max_length=255, blank=True, null=True, help_text=u'群组名称') | |
| 21 | + group_desc = models.TextField(_(u'group_desc'), blank=True, null=True, help_text=u'群组描述') | |
| 22 | + group_from = models.IntegerField(_(u'group_from'), choices=GROUP_FROM, default=APP_GROUP, help_text=u'群组来源') | |
| 23 | + session_id = models.CharField(_(u'session_id'), max_length=255, blank=True, null=True, help_text=u'照片组唯一标识', db_index=True) | |
| 24 | + group_lock = models.BooleanField(_(u'group_lock'), default=False, help_text=u'群组锁定') | |
| 25 | + | |
| 26 | + class Meta: | |
| 27 | + verbose_name = _(u'groupinfo') | |
| 28 | + verbose_name_plural = _(u'groupinfo') | |
| 29 | + | |
| 30 | + def __unicode__(self): | |
| 31 | + return unicode(self.group_id) | |
| 32 | + | |
| 33 | + @property | |
| 34 | + def users(self): | |
| 35 | + all_users = GroupUserInfo.objects.filter(group_id=self.group_id) | |
| 36 | + applying_users = all_users.filter(user_status=GroupUserInfo.APPLYING) | |
| 37 | + passed_users = all_users.filter(user_status=GroupUserInfo.PASSED) | |
| 38 | +        return { | |
| 39 | + 'applying_count': applying_users.count(), | |
| 40 | + 'passed_count': passed_users.count(), | |
| 41 | + 'applying': [applying.user_info for applying in applying_users], | |
| 42 | + 'passed': [passed.user_info for passed in passed_users], | |
| 43 | + } | |
| 44 | + | |
| 45 | + | |
| 46 | +class GroupUserInfo(CreateUpdateMixin): | |
| 47 | + APPLYING = 0 | |
| 48 | + PASSED = 1 | |
| 49 | + REFUSED = 2 | |
| 50 | + | |
| 51 | + USER_STATUS = ( | |
| 52 | + (APPLYING, u'申请中'), | |
| 53 | + (PASSED, u'已通过'), | |
| 54 | + (REFUSED, u'已拒绝'), | |
| 55 | + ) | |
| 56 | + | |
| 57 | + group_id = models.CharField(_(u'group_id'), max_length=255, blank=True, null=True, help_text=u'群组唯一标识', db_index=True) | |
| 58 | + user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识') | |
| 59 | + nickname = models.CharField(_(u'nickname'), max_length=255, blank=True, null=True, help_text=u'用户群组昵称') | |
| 60 | + admin = models.BooleanField(_(u'admin'), default=False, help_text=u'群组管理员') | |
| 61 | + user_status = models.IntegerField(_(u'user_status'), choices=USER_STATUS, default=APPLYING) | |
| 62 | + passed_at = models.DateTimeField(_(u'passed_at'), blank=True, null=True, help_text=_(u'通过时间')) | |
| 63 | + refused_at = models.DateTimeField(_(u'refused_at'), blank=True, null=True, help_text=_(u'拒绝时间')) | |
| 64 | + | |
| 65 | + class Meta: | |
| 66 | + verbose_name = _(u'groupuserinfo') | |
| 67 | + verbose_name_plural = _(u'groupuserinfo') | |
| 68 | + | |
| 69 | + def __unicode__(self): | |
| 70 | + return unicode(self.pk) | |
| 71 | + | |
| 72 | + @property | |
| 73 | + def user_info(self): | |
| 74 | +        return { | |
| 75 | + 'user_id': self.user_id, | |
| 76 | + 'nickname': self.nickname, | |
| 77 | + 'admin': self.admin, | |
| 78 | + } | |
| 79 | + | |
| 80 | + | |
| 81 | +class GroupPhotoInfo(CreateUpdateMixin): | |
| 82 | + group_id = models.CharField(_(u'group_id'), max_length=255, blank=True, null=True, help_text=u'群组唯一标识', db_index=True) | |
| 83 | + user_id = models.CharField(_(u'user_id'), max_length=255, blank=True, null=True, help_text=u'用户唯一标识') | |
| 84 | + nickname = models.CharField(_(u'nickname'), max_length=255, blank=True, null=True, help_text=u'用户群组昵称') | |
| 85 | + photo_path = models.CharField(_(u'photo_path'), max_length=255, blank=True, null=True, help_text=u'照片存放路径') | |
| 86 | + photo_thumbnail_path = models.CharField(_(u'photo_thumbnail_path'), max_length=255, blank=True, null=True, help_text=u'照片缩略图存放路径') | |
| 87 | + | |
| 88 | + class Meta: | |
| 89 | + verbose_name = _(u'groupuserinfo') | |
| 90 | + verbose_name_plural = _(u'groupuserinfo') | |
| 91 | + | |
| 92 | + def __unicode__(self): | |
| 93 | + return unicode(self.pk) | |
| 94 | + | |
| 95 | + @property | |
| 96 | + def photo_url(self): | |
| 97 | +        return u'{0}/{1}'.format(settings.IMG_DOMAIN, self.photo_path) if self.photo_path else '' | |
| 98 | + | |
| 99 | + @property | |
| 100 | + def photo_thumbnail_url(self): | |
| 101 | +        return u'{0}/{1}'.format(settings.IMG_DOMAIN, self.photo_thumbnail_path) if self.photo_thumbnail_path else '' | 
| @@ -0,0 +1,23 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from rest_framework import serializers | |
| 4 | + | |
| 5 | +from group.models import GroupInfo, GroupUserInfo, GroupPhotoInfo | |
| 6 | + | |
| 7 | + | |
| 8 | +class GroupInfoSerializer(serializers.HyperlinkedModelSerializer): | |
| 9 | + class Meta: | |
| 10 | + model = GroupInfo | |
| 11 | +        fields = ('group_id', 'group_name', 'group_desc', 'group_from', 'group_lock', 'status', 'created_at', 'updated_at') | |
| 12 | + | |
| 13 | + | |
| 14 | +class GroupUserInfoSerializer(serializers.HyperlinkedModelSerializer): | |
| 15 | + class Meta: | |
| 16 | + model = GroupUserInfo | |
| 17 | +        fields = ('group_id', 'user_id', 'nickname', 'admin', 'user_status', 'passed_at', 'refused_at', 'status', 'created_at', 'updated_at') | |
| 18 | + | |
| 19 | + | |
| 20 | +class GroupPhotoInfoSerializer(serializers.HyperlinkedModelSerializer): | |
| 21 | + class Meta: | |
| 22 | + model = GroupPhotoInfo | |
| 23 | +        fields = ('group_id', 'user_id', 'nickname', 'photo_path', 'photo_thumbnail_path', 'status', 'created_at', 'updated_at') | 
| @@ -0,0 +1,3 @@ | ||
| 1 | +from django.test import TestCase | |
| 2 | + | |
| 3 | +# Create your tests here. | 
| @@ -0,0 +1,106 @@ | ||
| 1 | +# -*- coding: utf-8 -*- | |
| 2 | + | |
| 3 | +from django.db import transaction | |
| 4 | +from django.http import JsonResponse | |
| 5 | + | |
| 6 | +from rest_framework import viewsets | |
| 7 | + | |
| 8 | +from account.models import UserInfo | |
| 9 | +from group.models import GroupInfo, GroupUserInfo, GroupPhotoInfo | |
| 10 | +from group.serializers import GroupInfoSerializer, GroupUserInfoSerializer, GroupPhotoInfoSerializer | |
| 11 | + | |
| 12 | +from utils.ip_utils import ip_addr | |
| 13 | + | |
| 14 | +from curtail_uuid import CurtailUUID | |
| 15 | +from TimeConvert import TimeConvert as tc | |
| 16 | + | |
| 17 | + | |
| 18 | +@transaction.atomic | |
| 19 | +def group_create_api(request): | |
| 20 | +    user_id = request.POST.get('user_id', '') | |
| 21 | + | |
| 22 | + try: | |
| 23 | + user = UserInfo.objects.get(user_id=user_id) | |
| 24 | + except UserInfo.DoesNotExist: | |
| 25 | +        return JsonResponse({ | |
| 26 | + 'status': 4011, | |
| 27 | + 'message': u'用户不存在', | |
| 28 | + }) | |
| 29 | + | |
| 30 | + group_id = CurtailUUID.uuid(GroupInfo, 'group_id') | |
| 31 | + group = GroupInfo.objects.create( | |
| 32 | + group_id=group_id, | |
| 33 | + group_from=GroupInfo.APP_GROUP, | |
| 34 | + ) | |
| 35 | + GroupUserInfo.objects.create( | |
| 36 | + group_id=group_id, | |
| 37 | + user_id=user_id, | |
| 38 | + nickname=user.username, | |
| 39 | + admin=True, | |
| 40 | + user_status=GroupUserInfo.PASSED, | |
| 41 | + passed_at=tc.utc_datetime(), | |
| 42 | + ) | |
| 43 | + | |
| 44 | +    return JsonResponse({ | |
| 45 | + 'status': 200, | |
| 46 | + 'message': u'群组创建成功', | |
| 47 | +        'data': { | |
| 48 | + 'group_id': group_id, | |
| 49 | + 'users': group.users | |
| 50 | + }, | |
| 51 | + }) | |
| 52 | + | |
| 53 | + | |
| 54 | +def group_join_api(request): | |
| 55 | +    group_id = request.POST.get('group_id', '') | |
| 56 | +    user_id = request.POST.get('user_id', '') | |
| 57 | +    nickname = request.POST.get('nickname', '') | |
| 58 | + | |
| 59 | + try: | |
| 60 | + user = UserInfo.objects.get(user_id=user_id) | |
| 61 | + except UserInfo.DoesNotExist: | |
| 62 | +        return JsonResponse({ | |
| 63 | + 'status': 4011, | |
| 64 | + 'message': u'用户不存在', | |
| 65 | + }) | |
| 66 | + | |
| 67 | + try: | |
| 68 | + group = GroupInfo.objects.get(group_id=group_id) | |
| 69 | + except GroupInfo.DoesNotExist: | |
| 70 | +        return JsonResponse({ | |
| 71 | + 'status': 4020, | |
| 72 | + 'message': u'群组不存在', | |
| 73 | + }) | |
| 74 | + if group.group_lock: | |
| 75 | +        return JsonResponse({ | |
| 76 | + 'status': 4021, | |
| 77 | + 'message': u'群组已锁定', | |
| 78 | + }) | |
| 79 | + | |
| 80 | + GroupUserInfo.objects.create( | |
| 81 | + group_id=group_id, | |
| 82 | + user_id=user_id, | |
| 83 | + nickname=nickname or user.username, | |
| 84 | + admin=False, | |
| 85 | + user_status=GroupUserInfo.APPLYING, | |
| 86 | + ) | |
| 87 | + | |
| 88 | +    return JsonResponse({ | |
| 89 | + 'status': 200, | |
| 90 | + 'message': u'申请已提交', | |
| 91 | + }) | |
| 92 | + | |
| 93 | + | |
| 94 | +class GroupInfoViewSet(viewsets.ModelViewSet): | |
| 95 | +    queryset = GroupInfo.objects.all().order_by('-created_at') | |
| 96 | + serializer_class = GroupInfoSerializer | |
| 97 | + | |
| 98 | + | |
| 99 | +class GroupUserInfoViewSet(viewsets.ModelViewSet): | |
| 100 | +    queryset = GroupUserInfo.objects.all().order_by('-created_at') | |
| 101 | + serializer_class = GroupUserInfoSerializer | |
| 102 | + | |
| 103 | + | |
| 104 | +class GroupPhotoInfoViewSet(viewsets.ModelViewSet): | |
| 105 | +    queryset = GroupPhotoInfo.objects.all().order_by('-created_at') | |
| 106 | + serializer_class = GroupPhotoInfoSerializer | 
| @@ -43,6 +43,7 @@ INSTALLED_APPS = ( | ||
| 43 | 43 | 'rest_framework', | 
| 44 | 44 | 'api', | 
| 45 | 45 | 'account', | 
| 46 | + 'group', | |
| 46 | 47 | 'photo', | 
| 47 | 48 | ) | 
| 48 | 49 |  | 
| @@ -22,13 +22,20 @@ from django.contrib import admin | ||
| 22 | 22 |  | 
| 23 | 23 | from rest_framework import routers | 
| 24 | 24 | from account import views as account_views | 
| 25 | +from group import views as group_views | |
| 25 | 26 | from photo import views as photo_views | 
| 26 | 27 |  | 
| 27 | 28 | router = routers.DefaultRouter() | 
| 28 | 29 | # router.register(r'users', account_views.UserViewSet) | 
| 29 | 30 | # router.register(r'groups', account_views.GroupViewSet) | 
| 31 | + | |
| 30 | 32 | router.register(r'lensmans', account_views.LensmanInfoViewSet) | 
| 31 | 33 | router.register(r'users', account_views.UserInfoViewSet) | 
| 34 | + | |
| 35 | +router.register(r'groups', group_views.GroupInfoViewSet) | |
| 36 | +router.register(r'group_users', group_views.GroupUserInfoViewSet) | |
| 37 | +router.register(r'group_photos', group_views.GroupPhotoInfoViewSet) | |
| 38 | + | |
| 32 | 39 | router.register(r'photos', photo_views.PhotoInfoViewSet) | 
| 33 | 40 |  | 
| 34 | 41 | urlpatterns = [ |